package com.magenta.maxoptra.gis.datasources;

import com.magenta.maxoptra.gis.commons.Logger;
import com.magenta.maxoptra.gis.datasources.jaxb.DataSourceSettingsType;
import com.magenta.maxoptra.gis.datasources.jaxb.DataSourceType;
import com.magenta.maxoptra.gis.datasources.jaxb.ObjectFactory;
import org.apache.logging.log4j.LogManager;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.*;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

/**
 * Contains information about external data sources, JNDI names, mappings of tables and columns
 *
 * @author Sergey Grachev
 */
public final class GisDataSources {
    private static final GisDataSource NULL_DATA_SOURCE = new GisDataSource();
    private static final Map<String, GisDataSource> DATA_SOURCES = new HashMap<String, GisDataSource>(0);
    private final static Logger LOGGER = new Logger(GisDataSources.class);

    private static URL settingsResource;
    private static volatile boolean initialized = false;

    private GisDataSources() {
    }

    /**
     * Override the default source of settings
     *
     * @param resource url to new settings
     */
    public static void useSettingsResource(final URL resource) {
        settingsResource = resource;
        reload();
    }

    /**
     * Reload settings
     */
    public static void reload() {
        synchronized (DATA_SOURCES) {
            initialize();
        }
    }

    /**
     * Get data source by internal name
     */
    public static GisDataSource get(final String alias) {
        synchronized (DATA_SOURCES) {
            if (!initialized) {
                initialize();
            }

            return DATA_SOURCES.containsKey(alias) ? DATA_SOURCES.get(alias) : NULL_DATA_SOURCE;
        }
    }

    private static void initialize() {
        initialized = false;

        InputStream stream;
        if ((stream = tryGetSettingsFromDefinedResource()) == null) {
            if ((stream = tryGetSettingsFromJboss()) == null) {
                if ((stream = tryGetSettingsFromInternalResource()) == null) {
                    LOGGER.debug("Settings not found");
                    return;
                }
            }
        }

        try {
            parseSettings(stream);
        } finally {
            try {
                stream.close();
            } catch (IOException ignore) {
            }
        }

        initialized = true;
    }

    private static InputStream tryGetSettingsFromDefinedResource() {
        if (settingsResource != null) {
            try {
                return settingsResource.openStream();
            } catch (IOException e) {
                LOGGER.error(e);
            }
        }
        return null;
    }

    private static InputStream tryGetSettingsFromJboss() {
        try {
            return new FileInputStream(new File(System.getProperty("jboss.home.dir"), "server/default/conf/gis-ds-settings.xml"));
        } catch (FileNotFoundException e) {
            LOGGER.error(e);
        }
        return null;
    }

    private static InputStream tryGetSettingsFromInternalResource() {
        return Thread.currentThread().getContextClassLoader().getResourceAsStream("gis-ds-settings.xml");
    }

    private static void parseSettings(final InputStream stream) {
        final DataSourceSettingsType dss;
        try {
            final JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);
            final Unmarshaller unmarshaller = context.createUnmarshaller();
            @SuppressWarnings("unchecked")
            final JAXBElement<DataSourceSettingsType> jaxbElement = (JAXBElement<DataSourceSettingsType>) unmarshaller.unmarshal(stream);
            dss = jaxbElement.getValue();
        } catch (JAXBException e) {
            LOGGER.error("Can't load settings of gis data sources", e);
            return;
        }

        for (final DataSourceType ds : dss.getDataSource()) {
            DATA_SOURCES.put(ds.getName(), new GisDataSource(ds));
        }
    }
}
